#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <unistd.h>
#include <errno.h>

#include <sys/socket.h>
#include <sys/epoll.h>

#include <arpa/inet.h>
#include <netinet/in.h>

#include "netconn.h"

#define MAX_EVENTS 10

int add_fd_to_epoll(struct epoll_event *ev,int epollfd,int fd,uint32_t event)
{
    	(*ev).events = event;
    	(*ev).data.fd = fd;
   	return epoll_ctl(epollfd,EPOLL_CTL_ADD, fd, ev);
}

int del_fd_from_epoll(int epollfd,int fd){
	return epoll_ctl(epollfd,EPOLL_CTL_DEL, fd, NULL);
}

/**
 * this buffer struct can be only seen by block loop for socket
 */
struct kernel_buffer{
	int len;
	char bytes[1024];
	int r_idx;
	int w_idx;
};

struct kernel_buffer* kernelBufferArr_new(size_t count);
int kernelBuffer_reset(int fd,struct kernel_buffer *arr_hdl);
/**
 *copy bytes from socket to the kernel_buffer.
 *return 0  nodata in socket 
 *return 0+ OK
 *return 0- ERR
 *
 */
int read_bytes_to_kernelBuffer(int fd,struct kernel_buffer *arr_hdl);
int kernelBuffer_compact(int fd,struct kernel_buffer *arr_hdl);


void start_netListen(struct sockaddr *addr,struct netevent_handlers *handlers,struct msgPackage_handlers *msgHandler)
{
	int listen_sock;
    	int ret;
    	listen_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    	if(listen_sock < 0){
		perror("socket failed");
		return ;
    	}
    
    	struct sockaddr_in client;
    	socklen_t client_len=sizeof(client);
    
    	ret=bind(listen_sock,addr,sizeof(struct sockaddr_in));
    	if(ret < 0){
		perror("bind failed");
		return ;
    	}
    
    	ret = listen(listen_sock,5);
    	if(ret < 0){
		perror("listen failed");
		return ;
    	}
    
    	struct epoll_event ev;
    	struct epoll_event events[MAX_EVENTS];


    	int conn_sock, nfds, epollfd;
    
    	epollfd = epoll_create(MAX_EVENTS);
    	if (epollfd == -1) {
		perror("epoll_create");
		exit(EXIT_FAILURE);
    	}
   

 	ret = add_fd_to_epoll(&ev,epollfd,listen_sock,EPOLLIN);
	if(ret < 0){
		perror("epoll_ctl");
		exit(EXIT_FAILURE);
	}

    	struct kernel_buffer *buffers_hdl= kernelBufferArr_new(MAX_EVENTS);
    	for (;;) {
		nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
		if (nfds == -1) {
			perror("epoll_pwait");
		    	exit(EXIT_FAILURE);
		}
		int n;
        	for (n = 0; n < nfds; ++n) {
		    	if (events[n].data.fd == listen_sock) {
				conn_sock = accept(listen_sock,(struct sockaddr *) &client, &client_len);
				if (conn_sock == -1) {
					perror("accept");
					exit(EXIT_FAILURE);
				}
                //setnonblocking(conn_sock);
				handlers->connected(conn_sock,(struct sockaddr *)&client);
				ret = add_fd_to_epoll(&ev,epollfd,conn_sock,EPOLLIN);
		    		if (ret < 0) {
			    		perror("epoll_ctl: conn_sock");
					exit(EXIT_FAILURE);
		    		}
				kernelBuffer_reset(conn_sock,buffers_hdl);
	    		} else {
				int fd=events[n].data.fd;
				int ret_read=read_bytes_to_kernelBuffer(fd,buffers_hdl);
				if(ret_read == -1){
					if(errno==EAGAIN){continue;}
					if(errno==EINTR){continue;}
					del_fd_from_epoll(epollfd,fd);
					close(fd);
					continue;
				}
				if(ret_read == 0){
					del_fd_from_epoll(epollfd,fd);
					close(fd);
					continue;
				}
//TODO 一次性读取一定的字节序列。拼包，发现断包，将前半个包的数据复制到下个缓冲区中。当前包有多少个消息，就抛出多少个事件，多个事件通用一个缓冲数据区；如果事件处理完毕对缓冲数据区去引用操作，当引用计数等于0时，收回。一次性全部读取数据，一次性全部拼包。
				while(1){
					int length_readable = buffers_hdl[fd-1].w_idx - buffers_hdl[fd-1].r_idx;
					if(length_readable==0){break;}
					char *bytes_begin = buffers_hdl[fd-1].bytes+buffers_hdl[fd-1].r_idx;
					void *msg_hdl;
					int moved=msgHandler->decoder(bytes_begin,length_readable,&msg_hdl);
					buffers_hdl[fd-1].r_idx += moved;
					if(moved == 0){
						if(buffers_hdl[fd-1].w_idx == buffers_hdl[fd-1].len){
							kernelBuffer_compact(fd,buffers_hdl);
						}
						
						break;
					}
					/*****send msg event *******/
				}
	    		}
		}
    }       
}

struct kernel_buffer* kernelBufferArr_new(size_t count){
	int size=sizeof(struct kernel_buffer);
	struct kernel_buffer *bufferArr=(struct kernel_buffer*)malloc(size * count);
	return bufferArr;
}

/**
 * this implemention is not good.if fd is big than the hdl_array's length. fd-1 may be out bound of array.
 * classic method is loop of finding a outtime element.
*/
int kernelBuffer_reset(int fd,struct kernel_buffer *arr_hdl){
	arr_hdl[fd-1].len=1024;
	arr_hdl[fd-1].r_idx=0;
	arr_hdl[fd-1].w_idx=0;
	return 0;
}

int read_bytes_to_kernelBuffer(int fd,struct kernel_buffer *arr_hdl){
	char *bytes_ref=arr_hdl[fd-1].bytes;
	int offset=arr_hdl[fd-1].w_idx;
	int length=arr_hdl[fd-1].len - arr_hdl[fd-1].w_idx;
	int readed = read(fd,(bytes_ref+offset),length);
	if(readed >0 ){
		arr_hdl[fd-1].w_idx += readed;
	}
	return readed;
}
int kernelBuffer_compact(int fd,struct kernel_buffer *arr_hdl){
	int offset = arr_hdl[fd-1].r_idx;
	int length = arr_hdl[fd-1].w_idx;
	memmove(arr_hdl[fd-1].bytes,(arr_hdl[fd-1].bytes+offset),(size_t)length);
	arr_hdl[fd-1].w_idx=length;
	arr_hdl[fd-1].r_idx=0;
	return 0;
}
